View Javadoc
1   package edu.jiangxin.apktoolbox.pdf.passwordremover;
2   
3   import edu.jiangxin.apktoolbox.pdf.PdfUtils;
4   import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
5   import edu.jiangxin.apktoolbox.swing.extend.FileListPanel;
6   import edu.jiangxin.apktoolbox.swing.extend.filepanel.FilePanel;
7   import edu.jiangxin.apktoolbox.utils.Constants;
8   import edu.jiangxin.apktoolbox.utils.FileUtils;
9   
10  import javax.swing.*;
11  import java.awt.event.ActionEvent;
12  import java.awt.event.ActionListener;
13  import java.io.File;
14  import java.util.ArrayList;
15  import java.util.List;
16  import java.util.Set;
17  import java.util.TreeSet;
18  import java.util.concurrent.ExecutorService;
19  import java.util.concurrent.Executors;
20  import java.util.concurrent.Future;
21  import java.util.concurrent.atomic.AtomicInteger;
22  
23  public class PdfPasswordRemoverPanel extends EasyPanel {
24      private FileListPanel fileListPanel;
25  
26      private JCheckBox isRecursiveSearched;
27  
28      private FilePanel targetDirPanel;
29  
30      private JButton startButton;
31      private JButton cancelButton;
32  
33      private JProgressBar progressBar;
34  
35      private WorkderThread searchThread;
36  
37      final private List<File> scannedFileList = new ArrayList<>();
38      @Override
39      public void initUI() {
40          BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS);
41          setLayout(boxLayout);
42  
43          createInputPanel();
44          add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
45  
46          createOutputPanel();
47          add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
48  
49          createOptionPanel();
50          add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
51  
52          createOperationPanel();
53          add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
54  
55          createProcessBarPanel();
56      }
57  
58      private void createInputPanel() {
59          fileListPanel = new FileListPanel();
60          add(fileListPanel);
61      }
62  
63      private void createOutputPanel() {
64          targetDirPanel = new FilePanel("Target Directory");
65          targetDirPanel.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
66          add(targetDirPanel);
67      }
68  
69      private void createOptionPanel() {
70          JPanel optionPanel = new JPanel();
71          optionPanel.setLayout(new BoxLayout(optionPanel, BoxLayout.X_AXIS));
72          optionPanel.setBorder(BorderFactory.createTitledBorder("Options"));
73  
74          isRecursiveSearched = new JCheckBox("Recursive Search");
75          isRecursiveSearched.setSelected(true);
76          optionPanel.add(isRecursiveSearched);
77          optionPanel.add(Box.createHorizontalGlue());
78  
79          add(optionPanel);
80      }
81  
82      private void createOperationPanel() {
83          JPanel operationPanel = new JPanel();
84          operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
85          operationPanel.setBorder(BorderFactory.createTitledBorder("Operations"));
86  
87          startButton = new JButton("Start");
88          cancelButton = new JButton("Cancel");
89          cancelButton.setEnabled(false);
90          startButton.addActionListener(new OperationButtonActionListener());
91          cancelButton.addActionListener(new OperationButtonActionListener());
92          operationPanel.add(startButton);
93          operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
94          operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
95          operationPanel.add(cancelButton);
96          operationPanel.add(Box.createHorizontalGlue());
97  
98          add(operationPanel);
99      }
100 
101     private void createProcessBarPanel() {
102         progressBar = new JProgressBar();
103         progressBar.setStringPainted(true);
104         progressBar.setString("Ready");
105 
106         add(progressBar);
107     }
108 
109     private void processFile(File encryptedFile, File targetDir) {
110         try {
111             if (Thread.currentThread().isInterrupted()) {
112                 return;
113             }
114             PdfUtils.removePasswordWithIText(encryptedFile, targetDir);
115         } catch (Throwable t) {
116             logger.error("Process file failed: {}", encryptedFile.getAbsolutePath(), t);
117         }
118     }
119 
120     class OperationButtonActionListener implements ActionListener {
121         @Override
122         public void actionPerformed(ActionEvent e) {
123             Object source = e.getSource();
124             if (source.equals(startButton)) {
125                 startButton.setEnabled(false);
126                 cancelButton.setEnabled(true);
127                 searchThread = new WorkderThread(isRecursiveSearched.isSelected());
128                 searchThread.start();
129             } else if (source.equals(cancelButton)) {
130                 startButton.setEnabled(true);
131                 cancelButton.setEnabled(false);
132                 if (searchThread.isAlive()) {
133                     searchThread.interrupt();
134                     searchThread.executorService.shutdownNow();
135                 }
136             }
137 
138         }
139     }
140 
141     class WorkderThread extends Thread {
142         public final ExecutorService executorService;
143         private final AtomicInteger processedFiles = new AtomicInteger(0);
144         private int totalFiles = 0;
145         private final boolean isRecursiveSearched;
146 
147         public WorkderThread(boolean isRecursiveSearched) {
148             super();
149             this.isRecursiveSearched = isRecursiveSearched;
150             this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
151 
152             SwingUtilities.invokeLater(() -> {
153                 progressBar.setValue(0);
154                 progressBar.setString("Starting remove process...");
155             });
156         }
157 
158         @Override
159         public void run() {
160             try {
161                 List<File> fileList = fileListPanel.getFileList();
162                 Set<File> fileSet = new TreeSet<>();
163                 String[] extensions = new String[]{"pdf", "PDF"};
164                 for (File file : fileList) {
165                     fileSet.addAll(FileUtils.listFiles(file, extensions, isRecursiveSearched));
166                 }
167 
168                 List<Future<?>> futures = new ArrayList<>();
169                 totalFiles = fileSet.size();
170                 updateProgress();
171 
172                 for (File file : fileSet) {
173                     if (Thread.currentThread().isInterrupted()) {
174                         return;
175                     }
176                     futures.add(executorService.submit(() -> {
177                         if (Thread.currentThread().isInterrupted()) {
178                             return null;
179                         }
180                         processFile(file, targetDirPanel.getFile());
181                         incrementProcessedFiles();
182                         return null;
183                     }));
184                 }
185 
186                 // Wait for all tasks to complete
187                 for (Future<?> future : futures) {
188                     try {
189                         future.get();
190                     } catch (InterruptedException e) {
191                         logger.error("Remove process interrupted", e);
192                         Thread.currentThread().interrupt(); // Restore interrupted status
193                         return;
194                     }
195                 }
196             } catch (Exception e) {
197                 logger.error("Remove process failed", e);
198                 SwingUtilities.invokeLater(() -> progressBar.setString("Remove process failed"));
199             } finally {
200                 executorService.shutdown();
201                 SwingUtilities.invokeLater(() -> {
202                     startButton.setEnabled(true);
203                     cancelButton.setEnabled(false);
204                 });
205             }
206         }
207 
208         private void incrementProcessedFiles() {
209             processedFiles.incrementAndGet();
210             updateProgress();
211         }
212 
213         private void updateProgress() {
214             if (totalFiles > 0) {
215                 SwingUtilities.invokeLater(() -> {
216                     int processed = processedFiles.get();
217                     int percentage = (int) ((processed * 100.0) / totalFiles);
218                     progressBar.setValue(percentage);
219                     progressBar.setString(String.format("Processing: %d/%d files (%d%%)", processed, totalFiles, percentage));
220                 });
221             }
222         }
223     }
224 }